#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
Simplified BSD License (BSD)
THN HTTP Stack Library
Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this 
      list of conditions and the following disclaimer.

    * Redistributions in binary form must reproduce the above copyright notice, 
      this list of conditions and the following disclaimer in the documentation 
      and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
POSSIBILITY OF SUCH DAMAGE.
*/
#endregion

#region Record of Change
//             							CHANGE HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | DEVELOPER  | DESCRIPTION                                              |
// -------------------------------------------------------------------------------------
// | 11-Nov-12 | HaiNM      | First creation.                                          |
// -------------------------------------------------------------------------------------
//
#endregion

#region Record of Audit
//             							REVIEW HISTORY
// -------------------------------------------------------------------------------------
// |   DATE    | REVIEWER   | NOTES                                                    |
// -------------------------------------------------------------------------------------
// -------------------------------------------------------------------------------------
//
#endregion

#region Using directives
using System;
using System.Net;

using Thn;
using Thn.Collections;
using Thn.Net.Sockets.Tcp;
#endregion

namespace Thn.Net.Http
{
    /// <summary>
    /// A simple to use HTTP server.
    /// </summary>
#if DEBUG || TESTING
    public
#else
    public
#endif
 class SimpleHttpServer : SimpleServerBase
    {
        #region Events

        #region Request Processed
        /// <summary>
        /// This event is raised after a request has been received and processed.
        /// </summary>
        public event HttpRequestEventHandler RequestProcessed;

        /// <summary>
        /// Invoke event RequestProcessed.
        /// </summary>        
        protected void RaiseEventRequestProcessed(IHttpRequestHandlingContext context)
        {
            HttpRequestEventHandler handler = RequestProcessed;
            if (handler != null) handler(this, context);
        }
        #endregion

        #endregion

        #region Fields
        /// <summary>
        /// The listener used for handling client connection request.
        /// </summary>
        TcpListener mListener;
        /// <summary>
        /// The signature of this server to return in every responses.
        /// </summary>
        protected string mServerSignature = "THN Solutions Simple Http Server";
        #endregion

        #region Properties

        #region Request Handlers
        private List<IHttpRequestHandler> mRequestHandlers = new List<IHttpRequestHandler>();
        /// <summary>
        /// Gets the list of handlers responsible for processing incoming requests.
        /// </summary>
        public IList<IHttpRequestHandler> RequestHandlers
        {
            get { return mRequestHandlers; }
        }
        #endregion

        #region Hostname
        /// <summary>
        /// Gets or sets name or address of the server.
        /// <para>If the property ConnectToEndPoint is blank, it will be built automatically from this hostname.</para>
        /// </summary>
        public string Hostname
        {
            get { return mListener.Hostname; }
            set { mListener.Hostname = value; }
        }
        #endregion

        #region Port
        /// <summary>
        /// Gets or sets port number of the server.
        /// <para>If the property ConnectToEndPoint is blank, it will be built automatically from this port.</para>
        /// </summary>
        public int Port
        {
            get { return mListener.Port; }
            set { mListener.Port = value; }
        }
        #endregion

        #region Address Family
        /// <summary>
        /// Gets or sets type of endpoint address.
        /// <para>If the property ConnectToEndPoint is blank, it will be built automatically from this family.</para>
        /// </summary>
        public System.Net.Sockets.AddressFamily AddressFamily
        {
            get { return mListener.AddressFamily; }
            set { mListener.AddressFamily = value; }
        }
        #endregion

        #region Listen On Endpoint
        /// <summary>
        /// Gets or sets the <see cref="EndPoint"/> to listen for incoming connections.
        /// </summary>
        public EndPoint ListenOnEndpoint
        {
            get { return mListener.ListenOnEndpoint; }
            set { mListener.ListenOnEndpoint = value; }
        }
        #endregion

        #endregion

        #region On Start
        /// <summary>
        /// Start this server.
        /// </summary>
        /// <param name="callback">The callback to raised after this operation completes. (Optional)</param>
        protected override void OnStart(ActionCallback callback)
        {
            #region Pre-conditions
            if (mRequestHandlers.Count == 0) throw new Exception("Must have at least one request handler");
            #endregion

            base.OnStart(callback);
        }
        #endregion

        #region On Process Request
        /// <summary>
        /// This method is raised to process an incoming HTTP request.
        /// </summary>
        /// <param name="channel">The channel that the request was sent to.</param>
        /// <param name="request">The received request.</param>
        protected virtual void OnProcessRequest(IHttpRequestHandlingContext context)
        {
            var path = context.Request.Path;
            int count = mRequestHandlers.Count;

            //iterate through all request and 
            for (int i = 0; i < count; i++)
            {
                mRequestHandlers[i].Process(context);

                //stop if the request is handled
                if (context.IsHandled) break;
            }
        }
        #endregion

        #region Process Request
        /// <summary>
        /// Process an incoming HTTP request.
        /// </summary>
        /// <param name="channel">The channel that the request was sent to.</param>
        /// <param name="request">The received request.</param>
        [System.Diagnostics.DebuggerHidden]
        void ProcessRequest(IChannel channel, IHttpRequest request)
        {
            try
            {
                //get handling context
                HttpRequestHandlingContext context = channel.Session.Parameters[this] as HttpRequestHandlingContext;
                if (context == null)
                {
                    #region Unavailable -> create one

                    context = new HttpRequestHandlingContext();
                    context.Channel = channel;

                    #endregion
                }
                #region Defensive tracing
                else
                {
                    //already created, skip
                }
                #endregion

                //reset context
                context.Reset();

                //process request
                context.Request = request;
                OnProcessRequest(context);

                //process response
                ProcessResponse(context);
            }
            catch (Exception error)
            {
                HttpResponse response = new HttpResponse();
                response.Status = HttpResponseStatus.InternalServerError;
                channel.Send(response);

#if DEBUG
                Log.Error(error);
                throw;
#endif
            }
        }
        #endregion

        #region Process Response
        void ProcessResponse(HttpRequestHandlingContext context)
        {
            bool requireDisconnect = !context.Request.KeepAlive;

            //retrieve the response to send
            IHttpResponse response;
            if (context.Response != null)
            {
                #region Has a response -> use it
                response = context.Response;
                #endregion
            }
            else
            {
                #region No response -> create one based on status
                HttpResponse r = new HttpResponse();
                r.Status = context.ResponseStatus;
                r.Body.Writer.WriteLine(string.Format("{0}", r.Status));
                response = r;
                context.Response = r;

                //disconnect this client because none of my handlers can understand it
                //requireDisconnect = true;
                #endregion
            }

            //set the server signature
            response.Headers[HttpResponseHeaderNames.Server] = mServerSignature;

            //raise event
            RaiseEventRequestProcessed(context);

            //send if required
            if (context.AutoSend)
            {
                context.Channel.Send(response);
            }
            #region Defensive tracing
            else
            {
                //not required, skip
            }
            #endregion

            //disconnect if required
            if (requireDisconnect)
            {
                context.Channel.Close();
            }

            //reset
            //context.Reset();
        }
        #endregion

        #region On Message Received
        /// <summary>
        /// This method is raised after a new message has been received for the provided channel.
        /// </summary>
        /// <param name="sender">The connector that raises this notification.</param>
        /// <param name="channel">The channel that the message is delivered from.</param>
        /// <param name="message">The newly received message.</param>
        protected override void OnMessageReceived(object sender, IChannel channel, object message)
        {
            ProcessRequest(channel, (IHttpRequest)message);
        }
        #endregion

        #region On Get Listener
        /// <summary>
        /// Retrieve the listener responsible for listening for client connections.
        /// </summary>
        protected override IListener OnGetListener()
        {
            return mListener;
        }
        #endregion

        #region Register Message Transformers
        void RegisterMessageTranformers()
        {
            #region Incoming Transformers

            IncomingTransformers.Push(new BufferToStreamMessageTransformer());
            IncomingTransformers.Push(new IncomingHttpRequestTransformer());

            #endregion

            #region Outgoing Transformers

            OutgoingTransformers.Push(new SerializableToStreamMessageTransformer());

            #endregion
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor.
        /// </summary>
        public SimpleHttpServer()
            : base()
        {
            RegisterMessageTranformers();
            mListener = new TcpListener();
            ((IListener)mListener).Host = this;
        }
        #endregion
    }
}
